package chapter21.section8;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @author liuziyang
 * @create 2019-02-06 18:14
 */
class Car3 {
    private final int id;
    private boolean engine = false, driveTrain = false, wheels = false;

    public Car3(int idn) {
        this.id = idn;
    }

    public Car3() {
        this.id = -1;
    }

    public synchronized int getId() {
        return this.id;
    }

    public synchronized void addEngine() {
        this.engine = true;
    }

    public synchronized void addDriveTrain() {
        this.driveTrain = true;
    }

    public synchronized void addWheels() {
        this.wheels = true;
    }

    @Override
    public String toString() {
        return "Car3 " + id + " [engine: " + engine + " driveTrain: " + driveTrain + " wheels: " + wheels + "]";
    }
}

class carQueue extends LinkedBlockingQueue<Car3> {

}

class ChassisBuilder implements Runnable {
    private final carQueue carQueue;
    private int counter = 0;

    public ChassisBuilder(chapter21.section8.carQueue carQueue) {
        this.carQueue = carQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.MILLISECONDS.sleep(500);
                Car3 car = new Car3(counter++);
                System.out.println("ChassisBuilder created " + car);
                carQueue.put(car);
            }
        } catch (InterruptedException e) {
            System.out.println("Interrupted: ChassisBuilder");
        }

        System.out.println("ChassisBuilder off");
    }
}

class Assembler implements Runnable {
    private final carQueue chassisQueue;
    private final carQueue finishingQueue;
    private Car3 car;
    private final CyclicBarrier barrier = new CyclicBarrier(4);
    private final RobotPool robotPool;

    public Assembler(carQueue chassisQueue, carQueue finishingQueue, RobotPool robotPool) {
        this.chassisQueue = chassisQueue;
        this.finishingQueue = finishingQueue;
        this.robotPool = robotPool;
    }

    public Car3 car() {
        return car;
    }

    public CyclicBarrier barrier() {
        return barrier;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                car = chassisQueue.take();
                robotPool.hire(EngineRobot.class, this);
                robotPool.hire(DriveTrainRobot.class, this);
                robotPool.hire(WheelRobot.class, this);
                barrier.await();
                finishingQueue.put(car);
            }
        } catch (InterruptedException e) {
            System.out.println("Exiting assembler via interrupt");
        } catch (BrokenBarrierException e) {
            throw new RuntimeException(e);
        }

        System.out.println("Assembler off");
    }
}

class Reporter implements Runnable {
    private final carQueue carQueue;

    public Reporter(chapter21.section8.carQueue carQueue) {
        this.carQueue = carQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println(carQueue.take());
            }
        } catch (InterruptedException e) {
            System.out.println("Exiting reporter via interrupt");
        }

        System.out.println("Reporter off");
    }
}

abstract class Robot implements Runnable {
    private final RobotPool pool;
    protected Assembler assembler;

    public Robot(RobotPool pool) {
        this.pool = pool;
    }

    public Robot assignAssembler(Assembler assembler) {
        this.assembler = assembler;
        return this;
    }

    private boolean engage = false;

    public synchronized void engage() {
        engage = true;
        notifyAll();
    }

    abstract protected void performService();

    @Override
    public void run() {
        try {
            powerDown();
            while (!Thread.interrupted()) {
                performService();
                assembler.barrier().await();
                powerDown();
            }
        } catch (InterruptedException e) {
            System.out.println("exiting" + this + " via interrupt");
        } catch (BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
        System.out.println(this + " off");
    }

    private synchronized void powerDown() throws InterruptedException {
        engage = false;
        assembler = null;
        pool.release(this);
        while (!engage) {
            wait();
        }
    }

    @Override
    public String toString() {
        return getClass().getName();
    }
}

class RobotPool {
    private final Set<Robot> pool = new HashSet<>();

    public synchronized void add(Robot robot) {
        pool.add(robot);
        notifyAll();
    }

    public synchronized void hire(Class<? extends Robot> robotType, Assembler d) throws InterruptedException {
        for (Robot robot : pool) {
            if (robot.getClass().equals(robotType)) {
                pool.remove(robot);
                robot.assignAssembler(d);
                robot.engage();
                return;
            }
        }
        wait();
        hire(robotType, d);
    }

    public synchronized void release(Robot robot) {
        add(robot);
    }
}

class EngineRobot extends Robot {
    public EngineRobot(RobotPool pool) {
        super(pool);
    }

    @Override
    protected void performService() {
        System.out.println(this + " install engine");
        assembler.car().addEngine();
    }
}

class DriveTrainRobot extends Robot {

    public DriveTrainRobot(RobotPool pool) {
        super(pool);
    }

    @Override
    protected void performService() {
        System.out.println(this + " install DriveTrain");
        assembler.car().addDriveTrain();
    }
}

class WheelRobot extends Robot {

    public WheelRobot(RobotPool pool) {
        super(pool);
    }

    @Override
    protected void performService() {
        System.out.println(this + " install Wheels");
        assembler.car().addWheels();
    }
}

public class CarBuilder {
    public static void main(String[] args) throws InterruptedException {
        carQueue chassisQueue = new carQueue(), finishingQueue = new carQueue();
        ExecutorService exec = Executors.newCachedThreadPool();
        RobotPool pool = new RobotPool();
        exec.execute(new EngineRobot(pool));
        exec.execute(new DriveTrainRobot(pool));
        exec.execute(new WheelRobot(pool));
        exec.execute(new Assembler(chassisQueue, finishingQueue, pool));
        exec.execute(new Reporter(finishingQueue));
        exec.execute(new ChassisBuilder(chassisQueue));
        TimeUnit.SECONDS.sleep(7);
        exec.shutdownNow();
    }
}
